#include "kmem.h"
#include "print.h"
#include "kernel.h"
#include "lib.h"
#include "mmu.h"
#include "process.h"
#include "csr.h"

static struct proc_block proc_table[CONFIG_SYS_PROCESS_NUM]; // 进程控制块表
static int nextpid = 1; // 进程ID分配器
struct proc_block *proc_current; // 当前进程控制块
struct proc_block *proc_ready; // 新就绪进程

#define PROC_TICK_DEFAULT (20) // 默认进程运行时间,单位为tick

// 初始化进程控制块
void os_proc_init(void)
{
    int i = 0;
    struct proc_block *p, *next;
  
    for (p = proc_table, next = p + 1; p < &proc_table[CONFIG_SYS_PROCESS_NUM]; i++, p++, next++) {
        // 申请内核栈空间
        p->kstack = os_kalloc();
        if (!p->kstack) {
         panic("proc kstack alloc");
        }

        // 初始化单向循环链表
        if (p != &proc_table[CONFIG_SYS_PROCESS_NUM - 1]) {
            p->next = next;
        } else {
            p->next = proc_table;
        }
    }
}

// 申请空闲进程控制块
static struct proc_block* proc_alloc(void)
{
    struct proc_block *p;
    unsigned long flag;

    // 尝试寻找空闲的进程控制块
    flag = os_int_disable();
    for(p = proc_table; p < &proc_table[CONFIG_SYS_PROCESS_NUM]; p++) {
        if(p->state == PROC_UNUSED) 
            goto found;
    }
    os_int_enable(flag);

    // 未找到空闲的进程控制块
    return 0;

    // 找到空闲进程控制块后进行初步初始化
found:
    p->pid   = nextpid++;
    p->state = PROC_USED;
    p->tick  = PROC_TICK_DEFAULT;

    memset(p->kstack, 0, PAGE_SIZE);

    os_int_enable(flag);
    return p;
}

// 创建进程
int os_proc_create(void(*proc)(void))
{
    struct proc_block *p;
    struct proc_context *ctx;
    unsigned long flag;

    p = proc_alloc();
    if (!p) {
        pr_err("proc_create fail!\n");
        return -1;
    }

    flag = os_int_disable();
    // 构造进程上下文
    ctx = (struct proc_context *)(((u64)p->kstack + PAGE_SIZE) - sizeof(struct proc_context));
    ctx->sp   = (u64)ctx;
    ctx->sepc = (u64)proc;
    ctx->ra = (u64)proc;
    p->state  = PROC_RUNNABLE;
    p->kstack = (void *)ctx->sp;
    os_int_enable(flag);

    return 0;
}

// 寻找新进程运行,必须在关中断环境下调用
static struct proc_block *proc_find_new(void)
{
    struct proc_block *new = proc_current->next;
    struct proc_block *old = proc_current;

    if (old->tick) { 
        // 当前进程时间片未用完,则继续运行
        new = old;
    } else {
        // 当前进程时间片未用完,寻找新的准备就绪的进程
        old->tick  = PROC_TICK_DEFAULT;
        old->state = PROC_RUNNABLE;

        for(; new != old; new = new->next) {
            if(new->state == PROC_RUNNABLE) {
                new->state = PROC_RUNNING;
                break;
            }
        }
    }

    return new;
}

// 尝试进程调度,禁止在中断中掉用
void os_sched(void)
{
    unsigned long flag;

    flag = os_int_disable();
    proc_ready = proc_find_new();
    // 新进程不是当前进程就执行上下文切换
    if (proc_ready != proc_current) {
        // 每次执行完 sret 后, SPP 位被自动清除,这里需要重新设置
        // SPP 设置为1,这样sret执行后处理器还是处于 S 模式
        csr_set(sstatus, MSTATUS_SPP);
        os_proc_ctx_switch();
    }
    os_int_enable(flag);
}

// 尝试进程调度,在中断中掉用,中断必须关闭
void os_sched_int(void)
{
    proc_ready = proc_find_new();
    // 新进程不是当前进程就执行上下文切换
    if (proc_ready != proc_current) {
        os_proc_int_ctx_sw();
    }
}

// 系统tick中断处理,必须在中断关闭时调用
void os_proc_tick_handle(void)
{
    struct proc_block *p = proc_current;

    if ((p->state == PROC_RUNNING) && p->tick) {
        p->tick--;
    }
}

// 主动放弃当前cpu,选择新进程运行
void os_yield(void)
{
    struct proc_block *p = proc_current;
    unsigned long flag;
    
    flag = os_int_disable();
    p->state = PROC_RUNNABLE;
    p->tick  = 0;
    os_int_enable(flag);

    os_sched();
}

// 运行内核,启动第一个进程
void os_start(void)
{
    struct proc_block *p = &proc_table[0];

    proc_ready = p;
    proc_ready->state = PROC_RUNNING;

    os_proc_int_ctx_sw();
}

static void process_test(void)
{
    while(1){
        pr_test("process run, pid = %d!\n", proc_current->pid);
        // 当未使用tick中断时,可以打开下面代码单独测试调度器
        // os_yield();
    }
}

void proc_test(void)
{
    os_proc_create(process_test);
    os_proc_create(process_test);
}